Skip to content

[python] avoid etag/match_condition clientName collision with multiple etag headers#10816

Open
l0lawrence wants to merge 4 commits into
microsoft:mainfrom
l0lawrence:fix/etag-multi-header-collision
Open

[python] avoid etag/match_condition clientName collision with multiple etag headers#10816
l0lawrence wants to merge 4 commits into
microsoft:mainfrom
l0lawrence:fix/etag-multi-header-collision

Conversation

@l0lawrence
Copy link
Copy Markdown
Member

Problem

PR #10494 (which added support for custom etag wire names) broke operations that have more than one Azure.Core.eTag-typed header. The Azure Storage Blob copyFromUrl operation is a concrete example — it carries:

  • standard If-Match / If-None-Match
  • custom x-ms-source-if-match / x-ms-source-if-none-match

The emitter in http.ts stamps etagRole = "ifMatch" / "ifNoneMatch" onto every one of those headers. Then in preprocess/__init__.py, update_parameter blindly applies headers_convert(ETAG_MATCH_DATA) / ETAG_NONE_MATCH_DATA to every parameter with that role — overwriting clientName to "etag" and "match_condition" on both pairs. The operation ends up with two parameters named etag and two named match_condition.

The slot-picker in update_client already chose one ifMatch/ifNoneMatch slot per operation, but it did nothing to prevent the per-parameter rename on the others.

Fix

In preprocess/__init__.py update_client:

  1. Collect all ifMatch and ifNoneMatch candidates per operation, not just the first.
  2. New _pick_etag_slot helper prefers the standard If-Match / If-None-Match wire names over custom etag headers (matches pre-PR-10494 behaviour when both are present).
  3. Strip etagRole from non-selected candidates so update_parameter leaves their natural clientName intact (e.g. source_if_match, source_if_none_match).

Result

For copyFromUrl:

  • If-Match / If-None-Match → promoted to the etag / match_condition pair (unchanged behaviour vs. pre-PR-10494).
  • x-ms-source-if-match / x-ms-source-if-none-match → keep their natural source_if_match / source_if_none_match client names.

No more clientName collisions.

Tests

Adds tests/unit/test_preprocess_etag.py with six tests:

  • test_etag_role_preserved_when_only_standard_pair_present
  • test_etag_role_preserved_when_only_custom_pair_present
  • test_standard_etag_wins_over_custom_when_both_present (regression)
  • test_first_custom_pair_chosen_when_multiple_custom_pairs_present
  • test_synthetic_partner_still_works_with_only_one_custom_etag
  • test_full_update_yaml_does_not_collide_client_names (end-to-end)

Verified the three multi-etag tests fail on upstream/main and pass with this change. All existing unit tests still pass.

Spec referenced

The original repro is copyFromUrl in specification/storage/Microsoft.BlobStorage/routes.tsp of Azure/azure-rest-api-specs, which composes SourceIfMatchParameter, SourceIfNoneMatchParameter, IfMatchParameter, and IfNoneMatchParameter (all Azure.Core.eTag).

…ion with multiple etag headers

When an operation has more than one Azure.Core.eTag-typed header (e.g. Storage's copyFromUrl, which carries both standard If-Match/If-None-Match AND custom x-ms-source-if-match/x-ms-source-if-none-match), PR microsoft#10494's per-parameter conversion renamed every etag-typed header to clientName='etag' or 'match_condition', producing two parameters with the same name on the generated method.

Fix: in preprocess update_client, collect all ifMatch/ifNoneMatch candidates per operation, prefer the standard If-Match/If-None-Match wire names for the etag/match_condition slot, and strip etagRole from the non-selected candidates so they retain their natural clientName (e.g. source_if_match).

Adds tests/unit/test_preprocess_etag.py covering the standard-only, custom-only, mixed (regression), multi-custom-pair, synthetic-partner, and end-to-end clientName uniqueness scenarios.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:client:python Issue for the Python client emitter: @typespec/http-client-python label May 27, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/http-client-python@10816

commit: 04aff1e

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

All changed packages have been documented.

  • @typespec/http-client-python
Show changes

@typespec/http-client-python - fix ✏️

Fix etag/match_condition clientName collision when an operation has more than one Azure.Core.eTag-typed header (e.g. Storage's copyFromUrl, which has both If-Match/If-None-Match and x-ms-source-if-match/x-ms-source-if-none-match). The standard If-Match/If-None-Match pair is now preferred for the etag/match_condition slot, and any additional etag-typed headers retain their natural client name (e.g. source_if_match).

@azure-sdk-automation
Copy link
Copy Markdown

azure-sdk-automation Bot commented May 27, 2026

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Copy link
Copy Markdown
Contributor

@msyyc msyyc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor suggestions on the etag-slot picker logic.

fall back to the first candidate. Returns None if there are no candidates.
"""
if not candidates:
return None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this early-return is redundant — iterating an empty list naturally falls through to return candidates[0], which would raise IndexError. Either drop the guard and rely on the loop (then handle the empty case at the call site), or keep it but note that candidates[0] is unreachable when empty. As-is it's just dead-code-ish.

Comment thread packages/http-client-python/generator/pygen/preprocess/__init__.py Outdated
Comment thread packages/http-client-python/generator/pygen/preprocess/__init__.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a regression in the Python HTTP client emitter preprocess step where operations with multiple Azure.Core.eTag-typed headers could end up with duplicate parameter clientNames (etag / match_condition). The change makes slot selection deterministic (prefer standard If-Match / If-None-Match) and demotes non-selected etag headers so they keep their natural names.

Changes:

  • Refactors etag header handling in PreProcessPlugin.update_client to (a) collect all candidates, (b) pick the promoted pair with a preference for standard wire names, and (c) strip etagRole from non-selected headers to avoid renaming collisions.
  • Adds unit tests covering standard/custom/multiple/custom-only/synthetic-partner scenarios and an end-to-end “no duplicate clientName” check.
  • Adds a Chronus changelog entry for the fix.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
packages/http-client-python/generator/pygen/preprocess/init.py Refactors etag pairing/selection logic and strips etagRole from non-selected headers to prevent clientName collisions.
packages/http-client-python/tests/unit/test_preprocess_etag.py Adds regression + edge-case tests ensuring correct slot selection and no clientName duplication.
.chronus/changes/fix-python-multi-etag-collision-2026-05-27-10-15-00.md Documents the bug fix for @typespec/http-client-python.

Comment on lines +10 to +20
def _plugin() -> PreProcessPlugin:
return PreProcessPlugin(
output_folder="",
options={
"version-tolerant": True,
"models-mode": "dpg",
"show-operations": True,
"show-send-request": True,
"builders-visibility": "public",
},
)
@l0lawrence l0lawrence marked this pull request as ready for review May 29, 2026 15:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:client:python Issue for the Python client emitter: @typespec/http-client-python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants